//
//  XPSandbox.swift
//  PPKit
//
//  Created by xiaopin on 2019/7/23.
//  Copyright © 2019 PPKit. All rights reserved.
//  https://gitee.com/xiaopindev/PPKit
//  https://github.com/xiaopindev
//  https://xiaopin.cnblogs.com
// Swift文件操作参考： https://www.hangge.com/blog/cache/detail_527.html
//

import UIKit

/// 沙盒目录类型
///
/// archives归档，images图片，videos视频，files文件，database数据库，imdatas聊天数据，缓存包，重要逻辑日志,语言包
enum XPSandboxDirType:String {
    case archives = "Archives"
    case images = "Images"
    case videos = "Videos"
    case files = "Files"
    case database = "Database"
    case imdatas = "IMDatas"
    case offlinePackages = "offlinePackages"
    case importantLogs = "importantLogs"
}

/// 本地沙盒操作
class XPSandbox{
    //MARK: 单例类写法
    static let shared = XPSandbox()
    private init(){}
    
    //MARK: 属性
    /// 本地沙盒 #文档# 路径
    lazy var docmentPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
    /// 本地沙盒 #缓存# 路径
    lazy var cachesPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
    /// 本地沙盒 #临时文件# 路径
    lazy var temporaryPath = NSTemporaryDirectory()

    //MARK: 文件夹和文件操作
    
    /// 根据文件夹和文件名构建档案路径,文件夹会自动创建
    /// - Parameters:
    ///   - dirType: 文档文件夹
    ///   - subPaths: 子文件夹目录subPath1/subPath2/
    ///   - fileName: 文件名
    ///   - inDoc: 是否在Documents目录，默认在Caches目录
    /// - Returns: 返回文件路径
    func buildPath(_ dirType:XPSandboxDirType, _ subPaths:String? = nil, _ fileName:String? = nil, _ inDoc:Bool = false) -> String{
        var dirPath = inDoc ? docmentPath! : cachesPath!
        dirPath += "/" + dirType.rawValue + "/"
        if let subPaths = subPaths {
            dirPath += subPaths
        }
        let isExists = FileManager.default.fileExists(atPath: dirPath)
        if !isExists {
            try? FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true)
        }
        if let fileName = fileName {
            dirPath += fileName
        }
        let filePath = dirPath.replacingOccurrences(of: "//", with: "/")
        return filePath
    }
    
    /// 根据文件夹和文件名构建档案路径,文件夹会自动创建
    /// - Parameters:
    ///   - dirType: 文档文件夹
    ///   - fileName: 文件名
    /// - Returns: 返回文件路径
    func buildDocsPath(_ dirType:XPSandboxDirType, _ fileName:String) -> String{
        //获取目录名称
        let dirPath = docmentPath! + "/" + dirType.rawValue + "/"
        //判断文件夹是否存在，不存在，则创建文件夹
        let isExists = FileManager.default.fileExists(atPath: dirPath)
        if !isExists {
            try? FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true)
        }
        //拼接文件路径
        let filePath = "\(dirPath)/\(fileName)".replacingOccurrences(of: "//", with: "/")
        
        return filePath
    }
    
    /// 根据文件夹和文件名构建缓存路径
    /// - Parameters:
    ///   - dirType: 文件夹
    ///   - fileName: 文件名
    /// - Returns: 返回缓存文件路径
    func buildCachesPath(_ dirType:XPSandboxDirType, _ fileName:String) -> String{
        //获取目录名称
        let dirPath = cachesPath! + "/" + dirType.rawValue + "/"
        //判断文件夹是否存在，不存在，则创建文件夹
        let isExists = FileManager.default.fileExists(atPath: dirPath)
        if !isExists {
            try? FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true)
        }
        //拼接文件路径
        let filePath = "\(dirPath)/\(fileName)".replacingOccurrences(of: "//", with: "/")
        //PPLog(filePath)
        return filePath
    }
    
    /// 文件或者目录是否存在
    /// - Parameter filePath: <#filePath description#>
    func isFileExists(_ filePath:String) -> Bool {
        return FileManager.default.fileExists(atPath: filePath)
    }
    
    /// 移除文件或文件夹
    /// - Parameter filePath: 文件或文件夹路径
    func removeFile(_ filePath:String){
        //不关心文件是否存在，直接删除，异常不处理
        try?FileManager.default.removeItem(atPath: filePath)
    }
    
    /// 移除文件或文件夹
    /// - Parameters:
    ///   - dirType: 文件夹
    ///   - fileName: 子文件夹或文件名
    ///   - inDoc: 是否是Docments目录，默认Caches目录
    func removeFile(_ dirType:XPSandboxDirType, _ fileName:String, _ inDoc:Bool = false){
        //构建文件路径
        var filePath = ""
        if inDoc {
            filePath = buildDocsPath(dirType, fileName)
        }else{
            filePath = buildCachesPath(dirType, fileName)
        }
        removeFile(filePath)
    }
    
    /// 模糊指定目录文件或子文件夹
    /// - Parameters:
    ///   - dirType: 文件夹
    ///   - like: 文件名包含关键字
    ///   - inDoc: 是否是Docments目录，默认Caches目录
    func removeFileWithLike(_ dirType:XPSandboxDirType, _ like:String?, _ inDoc:Bool = false){
        guard let str = like, str.isNotNullOrEmpty() else {return}
        var filePath = ""
        if inDoc {
            filePath = buildDocsPath(dirType, "")
        }else{
            filePath = buildCachesPath(dirType, "")
        }
        
        let subPaths = FileManager.default.subpaths(atPath: filePath)
        subPaths?.forEach({ (subPath) in
            if subPath.contains(find: str){
                removeFile(filePath+subPath)
            }
        })
    }
    
    /// 文件大小格式化to字符串
    ///
    /// - Parameter totalSize: <#totalSize description#>
    /// - Returns: <#return value description#>
    func fileSizeFormat(_ totalSize:Double) -> String {
        var size = totalSize
        var extIndex = 0
        let array = ["B","KB","MB","GB","TB","PB","EB","ZB","YB"]
        
        while totalSize > 1024 && extIndex < array.count {
            size = size/1024
            extIndex = extIndex + 1
        }
        
        if size < 1024 {
            return ""
        }
        return String(format: "%.2f", size)+array[extIndex]
    }
    
    /// 获取文件夹或者文件大小
    ///
    /// - Parameter filePath: <#filePath description#>
    /// - Returns: <#return value description#>
    func fileSizeAtPath(_ filePath:String) -> Double{
        let file = FileManager.default
        if file.fileExists(atPath: filePath){
            if let attr = try? FileManager.default.attributesOfItem(atPath: filePath) {
                return attr[.size] as! Double
            }
        }
        return 0
    }
    
    //MARK: Caches操作
    
    /*
     代码：
     //返回指定目录下的目录名和文件名数组
     let contentsOfPath = try? manager.contentsOfDirectory(atPath: url.path)
     //返回指定目录下的目录路径和文件路径数组
     let contentsOfURL = try? manager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
     
     //返回指定目录下的：子目录和文件，会递归所有子路
     let enumeratorAtPath = manager.enumerator(atPath: url.path)
     //同上，但包含了路径
     let enumeratorAtURL = manager.enumerator(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles, errorHandler:nil)
     
     let subPaths = manager.subpaths(atPath: url.path)
     */
    
    /// 获取缓存目录大小
    ///
    /// - Returns: <#return value description#>
    func readCacheSize() -> String {
        return fileSizeFormat(fileSizeAtPath(cachesPath!))
    }
    
    /// 移除Caches目录下所有子目录和子文件
    func removeAllCaches(){
        try?FileManager.default.removeItem(atPath: cachesPath!)
    }
    
    //MARK: Archives目录：归档&解档
    
    /// 归档数据到指定位置
    /// - Parameters:
    ///   - obj: <#obj description#>
    ///   - filePath: <#filePath description#>
    func archiveObject(_ obj:Any, toFile filePath:String){
        //存储:新的API可以支持自定义对象归档，但是要遵循NSSecureCoding协议
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true)
            do {
                _ = try data.write(to: URL(fileURLWithPath: filePath))
                //PPLog("写入成功")
            } catch {
                //PPLog("data写入本地失败: \(error)")
            }
        } catch  {
            //PPLog("模型转data失败: \(error)")
        }
    }
    /// 归档一个对象到Archives目录下
    /// - Parameters:
    ///   - obj: 归档的数据对象，系统自带的数据类型：字典，字符串，plist,Data, Int,数组等
    ///   - fileName: 文件名
    ///   - toDoc: 是否归档到Docments
    func archiveObject(_ obj:Any, fileName:String, _ toDoc:Bool = false){
        var filePath = ""
        if toDoc {
            filePath = buildDocsPath(.archives, fileName)
        }else{
            filePath = buildCachesPath(.archives, fileName)
        }
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true)
            do {
                _ = try data.write(to: URL(fileURLWithPath: filePath))
                //PPLog("写入成功")
            } catch {
                //PPLog("data写入本地失败: \(error)")
            }
        } catch  {
            //PPLog("模型转data失败: \(error)")
        }
    }
    
    func unarchiveObject(from filePath:String) -> Any?{
        do {
            let data = try Data.init(contentsOf: URL(fileURLWithPath: filePath))
            let result = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
            //PPLog("获取data数据成功: \(model)")
            return result
        } catch {
            //PPLog("获取data数据失败: \(error)")
        }
        return nil
    }

    /// 解档
    /// - Parameters:
    ///   - fileName: 文件名
    ///   - fromDoc: 是否来自Docments
    /// - Returns: 解档结果
    func unarchiveObject(_ fileName:String, _ fromDoc:Bool = false) -> Any?{
        //构建文件路径
        var filePath = ""
        if fromDoc {
            filePath = buildDocsPath(.archives, fileName)
        }else{
            filePath = buildCachesPath(.archives, fileName)
        }
        do {
            let data = try Data.init(contentsOf: URL(fileURLWithPath: filePath))
            let result = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
            //PPLog("获取data数据成功: \(model)")
            return result
        } catch {
            //PPLog("获取data数据失败: \(error)")
        }
        return nil
    }
    
    //MARK: Images目录：缓存&移除
    /// 缓存一个网络图片
    ///
    /// - Parameter url: <#url description#>
    func cacheImage(_ url:String, completionHandlder: @escaping (Bool, String) ->()){
        guard let imageURL = URL(string: url) else { return }
        let ext = imageURL.pathExtension
        var imageStorePath = ""
        if ["png","jpg","jpeg","gif","avif"].contains(ext){
            imageStorePath = XPSandbox.shared.buildCachesPath(.images, "\(url.MD5).\(ext)")
        }else{
            PPLog("非法图片路径")
            return
        }
        
        XPNetworkApi.shared.download(url: url, destination: imageStorePath) { (progress) in
            PPLog("\(url) 缓存进度:\(progress)")
        } finished: { (msg, nil, res) in
            PPLog("缓存图片完成:\(url)")
            completionHandlder(true,imageStorePath)
        } failed: { (msg, code) in
            PPLog("缓存本地图片失败")
            completionHandlder(false,imageStorePath)
        }
    }
    
    /// 获取缓存图片
    /// - Parameter url: 网络图片地址
    /// - Returns: <#description#>
    func getCacheImage(_ url:String) -> UIImage?{
        guard let imageURL = URL(string: url) else { return nil }
        let ext = imageURL.pathExtension
        var imageStorePath = ""
        if ["png","jpg","jpeg","gif","avif"].contains(ext){
            imageStorePath = XPSandbox.shared.buildCachesPath(.images, "\(url.MD5).\(ext)")
        }else{
            PPLog("非法图片路径")
            return nil
        }
        if isFileExists(imageStorePath) {
            return UIImage.init(contentsOfFile: imageStorePath)
        }
        return nil
    }
    
    /// 获取缓存图片路径
    /// - Parameter url: <#url description#>
    /// - Returns: <#description#>
    func getCacheImagePath(_ url:String) -> String?{
        guard let imageURL = URL(string: url) else { return nil }
        let ext = imageURL.pathExtension
        var imageStorePath = ""
        if ["png","jpg","jpeg","gif","avif"].contains(ext){
            imageStorePath = XPSandbox.shared.buildCachesPath(.images, "\(url.MD5).\(ext)")
        }else{
            PPLog("非法图片路径")
            return nil
        }
        if isFileExists(imageStorePath) {
            return imageStorePath
        }
        return nil
    }
    
    /// 移除缓存图片
    /// - Parameter url: <#url description#>
    func removeCacheImage(_ url:String){
        guard let imageURL = URL(string: url) else { return }
        let ext = imageURL.pathExtension
        var imageStorePath = ""
        if ["png","jpg","jpeg","gif","avif"].contains(ext){
            imageStorePath = XPSandbox.shared.buildCachesPath(.images, "\(url.MD5).\(ext)")
        }else{
            PPLog("非法图片路径")
            return
        }
        removeFile(imageStorePath)
    }
}
